#pragma once

#include <iostream>
#include <fstream>
#include <cstdio>
#include <string>
#include <ctime>
#include <cstdarg>
#include <sys/types.h>
#include <unistd.h>
#include <pthread.h>
#include "LockGuard.hpp"
// 往显示器打印.往文件中写入

// 全局变量：控制日志输出方式
bool gIsSave = false;                  // false表示输出到屏幕，true表示输出到文件
const std::string logname = "log.txt"; // 日志文件名

// 1. 日志是由等级的
// 定义日志级别枚举，从低到高依次为：调试、信息、警告、错误、致命错误
enum Level
{
    DEBUG = 0, // 调试信息，用于开发调试
    INFO,      // 一般信息，用于记录程序运行状态
    WARNING,   // 警告信息，表示潜在问题
    ERROR,     // 错误信息，表示程序错误
    FATAL      // 致命错误，可能导致程序崩溃
};

// 将日志内容保存到指定文件
// @param filename: 目标文件名
// @param message: 要保存的日志消息
void SaveFile(const std::string &filename, const std::string &message)
{
    // 1. 创建并打开文件流对象
    // std::ofstream out(filename, std::ios::app);
    // - filename: 字符串，表示要打开或创建的文件的路径和名称。
    //             std::ofstream 的构造函数可以接受 std::string 对象 (C++11 及以后)
    //             或 const char*。这里是 std::string，所以是安全的。
    // - std::ios::app: 打开模式标志。
    //   - 效果:
    //     a. 如果名为 `filename` 的文件不存在，系统会尝试创建一个新的空文件。
    //     b. 如果文件已存在，文件指针会定位到文件的当前末尾。
    //     c. 所有后续的写入操作 (通过 `out << ...`) 都会从这个末尾位置开始，
    //        从而将新的 `message` 追加到文件原有内容的后面，而不会覆盖旧内容。
    //   - 隐含 `std::ios::out`: `std::ios::app` 模式通常隐含了输出能力。
    //     显式写作 `std::ios::out | std::ios::app` 也是可以的，意义相同。
    std::ofstream out(filename, std::ios::app); // 以追加模式打开文件
    // 2. 检查文件是否成功打开
    // if (!out.is_open())
    // - out.is_open() 返回一个布尔值。如果文件成功打开并与流 `out` 关联，则返回 true。
    // - 如果打开失败 (例如，路径无效、没有写权限、磁盘已满等)，则返回 false。
    // - `!out.is_open()` 因此在打开失败时为 true。
    if (!out.is_open())
    {
        return;
    }
    // 3. 向文件写入消息
    // out << message;
    // - 使用流插入操作符 `<<` 将 `message` 字符串的内容写入到与 `out` 关联的文件中。
    // - 由于是以 std::ios::app 模式打开，写入操作会在文件的末尾进行。
    // - 注意：这里没有显式地写入换行符 (`std::endl` 或 `\n`)。
    //   如果期望每条日志消息占据新的一行，那么 `message` 字符串本身应该包含换行符，
    //   或者在写入时添加它，例如：`out << message << std::endl;`
    //   `std::endl` 不仅插入换行符，还会刷新 (flush) 输出缓冲区。
    out << message;
    // 4. 关闭文件 (可选但推荐)
    // out.close();
    // - 显式关闭文件。这会确保所有缓冲在内存中的数据被刷新到磁盘文件，
    //   并释放与该文件关联的系统资源。
    // - 如果不显式调用 `close()`，当 `out` 对象离开作用域（即 `SaveFile` 函数返回时），
    //   `std::ofstream` 的析构函数也会自动调用 `close()`。
    // - 显式关闭的好处：
    //   a. 可以更早地检测到关闭时可能发生的错误（虽然 `close()` 本身通常不被设计为报告所有类型的写入错误，错误主要在写入操作时通过流状态反映）。
    //   b. 如果之后需要立即对同一个文件进行其他操作（例如，用 `ifstream` 读取），确保文件已关闭并刷新是重要的。
    //   c. 代码意图更清晰。
    out.close();
}

// 将日志级别转换为对应的字符串
// @param level: 日志级别枚举值
// @return: 对应的日志级别字符串
std::string LevelToString(int level)
{
    switch (level)
    {
    case DEBUG:
        return "Debug";
    case INFO:
        return "Info";
    case WARNING:
        return "Warning";
    case ERROR:
        return "Error";
    case FATAL:
        return "Fatal";
    default:
        return "Unknown";
    }
}

// 获取当前时间的格式化字符串
// @return: 格式化的时间字符串，格式为：年-月-日 时:分:秒
std::string GetTimeString()
{
    // 1. 获取日历时间 (Calendar Time)
    // time_t curr_time = time(nullptr);
    // - time_t: 通常是一个整数类型 (如 long int 或 long long int)，
    //           用于表示从某个固定时间点 (称为纪元，Epoch) 到现在的秒数。
    //           POSIX 系统中，纪元通常是 1970-01-01 00:00:00 UTC (协调世界时)。
    // - time(nullptr): 这是 <ctime> (或 C 中的 <time.h>) 中的一个标准库函数。
    //   - 参数: 可以是一个指向 time_t 类型的指针。如果参数不是 nullptr，
    //           则该函数也会将获取到的时间值存储到该指针指向的内存位置。
    //   - 返回值: 返回当前的日历时间 (time_t 类型)。
    //   - 如果参数为 nullptr，函数仅返回时间值，不进行额外的存储。
    //   - 如果获取时间失败，函数返回 -1 (并可能设置 errno)。
    time_t curr_time = time(nullptr);
    // 2. 将日历时间转换为本地时间 (Broken-down Time)
    // struct tm *format_time = localtime(&curr_time);
    // - struct tm: 这是一个标准C结构体，用于表示“分解时间”或“细分时间”，
    //              即将日历时间分解为年、月、日、时、分、秒等单独的组成部分。
    //              其主要成员包括：
    //                int tm_sec;   // 秒 (0-60)，60 用于表示闰秒
    //                int tm_min;   // 分 (0-59)
    //                int tm_hour;  // 时 (0-23)
    //                int tm_mday;  // 月中的日 (1-31)
    //                int tm_mon;   // 月 (0-11)，0 代表一月，11 代表十二月
    //                int tm_year;  // 年，自1900年起的年数 (例如，2025年表示为 125)
    //                int tm_wday;  // 星期几 (0-6)，0 代表星期日
    //                int tm_yday;  // 年中的日 (0-365)，0 代表1月1日
    //                int tm_isdst; // 夏令时标志 (正数表示夏令时生效，0表示不生效，负数表示信息不可用)
    // - localtime(const time_t *timer): 标准库函数。
    //   - 参数: 一个指向 time_t 类型值的指针，该值表示要转换的日历时间。
    //   - 返回值: 一个指向静态分配的 `struct tm` 对象的指针。这个 `struct tm` 对象
    //             包含了根据本地时区和夏令时规则转换后的分解时间。
    //   - **重要注意事项 (线程安全和静态存储)**:
    //     `localtime` (以及 `gmtime`) 返回的指针指向一个静态分配的 `struct tm` 对象。
    //     这意味着后续对 `localtime` 或 `gmtime` 的调用可能会覆盖之前调用返回的 `struct tm` 对象的内容。
    //     因此，`localtime` **不是线程安全的**。如果多个线程同时调用它，可能会发生数据竞争。
    //     在多线程环境中，应使用其可重入版本，如 `localtime_r` (POSIX) 或 C++20 的 `<chrono>` 库。
    //     或者，如果只需要一个线程获取时间，则问题不大，但要小心在信号处理函数中调用它。
    //   - 如果转换失败 (例如 `curr_time` 的值无效)，`localtime` 返回 `nullptr`。
    struct tm *format_time = localtime(&curr_time);
    if (format_time == nullptr)
        return "None";
    // 4. 格式化时间字符串
    // char time_buffer[1024]; // 定义一个足够大的字符数组来存储格式化后的时间字符串
    // C++ 中更推荐使用 std::string 和 std::ostringstream，或者 <chrono> 和 <format> (C++20)
    // 但对于固定格式且长度可控的场景，字符数组和 snprintf 也是一种常见的C风格做法。
    char time_buffer[1024];
    // snprintf(buffer, size, format, ...): C标准库函数，用于安全地格式化字符串。
    // - buffer: 指向存储结果的字符数组的指针。
    // - size: buffer 的大小（包括末尾的空终止符 '\0'）。snprintf 会确保不写入超过 size-1 个字符，
    //         并在末尾添加 '\0'，从而防止缓冲区溢出。
    // - format: 格式控制字符串，与 printf 类似。
    // - ...: 与格式字符串中格式说明符对应的附加参数。
    // 返回值: 如果成功，返回将要写入（不包括末尾空字符）的字符数。如果输出被截断（因为size太小），
    //        它返回如果缓冲区足够大时本应写入的字符数。如果发生编码错误，返回负值。
    snprintf(time_buffer, sizeof(time_buffer), "%d-%d-%d %d:%d:%d",
             format_time->tm_year + 1900, // 年份需要加1900
             format_time->tm_mon + 1,     // 月份从0开始，需要加1
             format_time->tm_mday,
             format_time->tm_hour,
             format_time->tm_min,
             format_time->tm_sec);
    // 5. 返回格式化后的时间字符串
    // time_buffer 是一个C风格的字符数组 (以'\0'结尾的字符串)。
    // 将其隐式转换为 std::string 对象并返回。
    return time_buffer;
}

// 全局互斥锁，用于保护日志输出
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;

// 2. 日志是由格式的
// 日志等级 时间 代码所在的文件名/行数 日志的内容

// 核心日志记录函数
// @param filename: 调用日志函数的文件名
// @param line: 调用日志函数的行号
// @param issave: 是否保存到文件
// @param level: 日志级别
// @param format: 格式化字符串
// @param ...: 可变参数列表
void LogMessage(std::string filename, int line, bool issave, int level, const char *format, ...)
{
    // 获取日志级别字符串
    std::string levelstr = LevelToString(level);
    // 获取当前时间字符串
    std::string timestr = GetTimeString();
    // 获取当前进程ID
    pid_t selfid = getpid();

    // 处理可变参数
    // 1. 定义一个足够大的字符数组缓冲区，用于存储格式化后的日志消息
    // char buffer[1024];
    // - buffer: 一个静态分配的字符数组。它的大小 (1024字节) 决定了单条日志消息
    //           (格式化后的可变参数部分) 的最大长度。如果格式化后的字符串超过这个长度，
    //           vsnprintf 会截断输出以防止缓冲区溢出。
    // - 对于非常长的日志消息，这个缓冲区大小可能需要调整，或者考虑使用动态分配的缓冲区
    //   (例如，先用 vsnprintf 计算所需大小，然后分配，再格式化)，但这会增加复杂性。
    char buffer[1024];

    // 2. 声明一个 va_list 类型的变量，用于存储可变参数列表的信息
    // va_list arg;
    // - va_list: 这是 <cstdarg> (或 C 中的 <stdarg.h>) 中定义的一个特殊类型，
    //            用于配合 va_start, va_arg, va_end, va_copy 等宏来访问可变参数。
    //            它通常是一个指针或某种内部结构，具体实现依赖于编译器和平台。
    // - arg: 是 va_list 类型的一个变量，它将用于遍历函数接收到的可变参数。
    va_list arg;

    // 3. 初始化 va_list 变量，使其指向第一个可变参数
    // va_start(arg, format);
    // - va_start: 是一个宏，用于初始化 `arg`。
    //   - 第一个参数 (arg): 是要初始化的 `va_list` 变量。
    //   - 第二个参数 (format): 必须是函数参数列表中最后一个【固定】参数的名称。
    //                        `va_start` 使用这个固定参数的地址来定位第一个可变参数的位置。
    //                        在本函数 LogMessage(..., const char *format, ...) 中，
    //                        `format` 是最后一个固定参数。
    // - 调用 `va_start` 后，`arg` 就准备好被 `va_arg` 或 `vsnprintf` 等函数用来访问
    //   `format` 参数之后的可变参数了。
    // - 注意：必须在访问任何可变参数之前调用 `va_start`。
    va_start(arg, format);

    // 4. 使用 vsnprintf 将格式化字符串和可变参数列表组合成最终的日志消息字符串
    // vsnprintf(buffer, sizeof(buffer), format, arg);
    // - vsnprintf: 是 "variant" (v) "string" (s) "n" (counted) "printf"。
    //            它与 snprintf 类似，但它接受一个 `va_list` (arg) 作为参数，
    //            而不是直接接受可变数量的参数。这使得它非常适合在自定义的可变参数函数中使用。
    //   - 第一个参数 (buffer): 指向存储结果字符串的字符数组的指针。
    //   - 第二个参数 (sizeof(buffer)): `buffer` 的大小（字节数）。`vsnprintf` 会确保
    //                                 写入的字符数（包括末尾的空终止符 '\0'）不超过这个大小，
    //                                 从而防止缓冲区溢出。
    //   - 第三个参数 (format): 格式控制字符串，与 `printf` 的格式字符串相同。
    //                         它包含普通的字符和格式说明符 (如 %s, %d, %f)。
    //   - 第四个参数 (arg): 一个已经由 `va_start` 初始化的 `va_list` 变量，
    //                      它提供了与 `format` 字符串中格式说明符对应的实际参数值。
    // - 返回值: 如果成功且输出没有被截断，`vsnprintf` 返回将要写入 `buffer` 的字符数
    //          (不包括末尾的空字符 '\0')。
    //          如果输出因为 `buffer` 太小而被截断，它返回如果 `buffer` 足够大时
    //          本应写入的字符数。
    //          如果发生编码错误，它返回一个负值。
    // - 在这里，格式化后的字符串被写入到 `buffer` 中。
    vsnprintf(buffer, sizeof(buffer), format, arg);

    // 5. 清理 va_list 变量，释放其占用的资源
    // va_end(arg);
    // - va_end: 是一个宏，用于在函数处理完所有可变参数后进行必要的清理工作。
    //   - 参数 (arg): 是之前由 `va_start` 初始化的 `va_list` 变量。
    // - 调用 `va_end` 很重要，因为它可能会释放 `va_start` 分配的资源，或者进行其他
    //   平台相关的清理。确保在函数返回前，并且在 `va_list` 不再需要时调用它。
    // - 每次调用 `va_start` 都必须对应一次 `va_end` 的调用。
    va_end(arg);

    // 组装完整的日志消息
    std::string message = "[" + timestr + "]" + "[" + levelstr + "]" +
                          "[" + std::to_string(selfid) + "]" +
                          "[" + filename + "]" + "[" + std::to_string(line) + "] " + buffer + "\n";

    // 使用RAII方式加锁，确保线程安全
    LockGuard lockguard(&lock);

    // 根据issave决定输出目标
    if (!issave)
    {
        std::cout << message; // 输出到屏幕
    }
    else
    {
        SaveFile(logname, message); // 保存到文件
    }
}

// 日志宏定义，简化日志调用
// 使用do-while(0)结构确保宏定义的安全性
// ##__VA_ARGS__ 用于处理可变参数，当没有参数时自动去除逗号
#define LOG(level, format, ...)                                                \
    do                                                                         \
    {                                                                          \
        LogMessage(__FILE__, __LINE__, gIsSave, level, format, ##__VA_ARGS__); \
    } while (0)

// 启用文件日志输出
#define EnableFile()    \
    do                  \
    {                   \
        gIsSave = true; \
    } while (0)

// 启用屏幕日志输出
#define EnableScreen()   \
    do                   \
    {                    \
        gIsSave = false; \
    } while (0)

// 默认传递进来的参数都是整数
// void Test(int num, ...)
// {
//     va_list arg;
//     va_start(arg, num);

//     while(num)
//     {
//         int data = va_arg(arg, int);
//         std::cout << "data: " << data << std::endl;
//         num--;
//     }

//     va_end(arg); // arg = NULL
// }